/*********************************************************************************************************
* 模块名称：USART485.c
* 摘    要：串口模块，包括串口模块初始化，以及中断服务函数处理，以及读写串口函数实现
* 当前版本：1.0.0
* 作    者：
* 完成日期：2025年01月15日
* 内    容：包括串口DMA收发，以及支持485
* 注    意：
**********************************************************************************************************
* 取代版本：
* 作    者：
* 完成日期：
* 修改内容：
* 修改文件：
*********************************************************************************************************/

/*********************************************************************************************************
*                                              包含头文件
*********************************************************************************************************/
#include "USART485.h"
#include "gd32f30x_conf.h"
#include "Systick.h"
#include <stdint.h>
#include <stdio.h>
#include <stdbool.h>
#include <stdarg.h>
#include <string.h>

/*********************************************************************************************************
*                                              宏定义
*********************************************************************************************************/
#define USART0_DATA_ADDR      (uint32_t)&USART_DATA(USART0)   //串口0的数据寄存器十六进制地址值
						
#define MAX_BUF_SIZE            300 //串口收发数组大小
#define DMA_SEND_DELAY_COUNT    200 //DMA发送完成后，延时次数，每次延时2us

/*********************************************************************************************************
*                                              枚举结构体
*********************************************************************************************************/
typedef struct
{   
	uint32_t uartNo;            //串口号
	rcu_periph_enum rcuUart;    //串口时钟
	rcu_periph_enum rcuGpio;    //串口GPIO时钟
	uint32_t gpio;              //GPIO
	uint32_t txPin;             //发送Pin
	uint32_t rxPin;             //接收Pin
    uint32_t rePin;             //收发方向Pin
	uint8_t irq;                //串口中断号
	uint32_t dmaNo;             //DMA编号
	rcu_periph_enum rcuDma;     //DMA时钟
	dma_channel_enum dmaRxCh;   //DMA接收通道号
    dma_channel_enum dmaTxCh;   //DMA发送通道号
} UartHwInfo_t;

static uint8_t g_rcvDataBuf[MAX_BUF_SIZE];
static uint8_t g_traDataBuf[MAX_BUF_SIZE];
static bool g_pktRcvd = false;
static bool s_bDMASendEndStatus = false;
/*********************************************************************************************************
*                                              内部变量定义
*********************************************************************************************************/
static UartHwInfo_t g_uartHwInfo = 
{
	USART0,         //串口号
	RCU_USART0,     //串口时钟
	RCU_GPIOA,      //串口GPIO时钟
	GPIOA,          //GPIO
	GPIO_PIN_9,     //发送Pin
	GPIO_PIN_10,    //接收Pin
	GPIO_PIN_12,    //收发方向Pin
	USART0_IRQn,    //串口中断号
	DMA0,           //DMA编号
	RCU_DMA0,       //DMA时钟
	DMA_CH4,        //DMA接收通道号
    DMA_CH3         //DMA发送通道号
};

/*********************************************************************************************************
*                                              内部函数声明
*********************************************************************************************************/
static void Usart0GpioInit(void);
static void Usart0DmaInit(void);
static void Usart0Init(uint32_t baudRate);
static void DMASendData(uint16_t sendCount);
/*********************************************************************************************************
*                                              内部函数实现
*********************************************************************************************************/
/*********************************************************************************************************
* 函数名称：Usart0GpioInit
* 函数功能：初始化串口0的GPIO
* 输入参数：void
* 输出参数：void
* 返 回 值：void
* 创建日期：2025年01月15日
* 注    意：
*********************************************************************************************************/ 
static void Usart0GpioInit(void)
{
	rcu_periph_clock_enable(g_uartHwInfo.rcuGpio);
	gpio_init(g_uartHwInfo.gpio, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, g_uartHwInfo.txPin);
	gpio_init(g_uartHwInfo.gpio, GPIO_MODE_IPU, GPIO_OSPEED_50MHZ, g_uartHwInfo.rxPin);
    
    gpio_init(g_uartHwInfo.gpio, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ, g_uartHwInfo.rePin);    
    gpio_bit_reset(g_uartHwInfo.gpio, g_uartHwInfo.rePin); //默认低电平，接收模式
}

/*********************************************************************************************************
* 函数名称：Usart0DmaInit
* 函数功能：初始化串口0的DMA
* 输入参数：void
* 输出参数：void
* 返 回 值：void
* 创建日期：2025年01月15日
* 注    意：
*********************************************************************************************************/ 
static void Usart0DmaInit(void)
{      
	dma_parameter_struct dmaStruct;
    
	/* 使能DMA时钟；*/
	rcu_periph_clock_enable(g_uartHwInfo.rcuDma);
    
	/* 复位DMA通道；串口0接收 */
	dma_deinit(g_uartHwInfo.dmaNo, g_uartHwInfo.dmaRxCh);

    dma_struct_para_init(&dmaStruct);
    
	/* 配置传输方向；串口0接收 */ 
	dmaStruct.direction = DMA_PERIPHERAL_TO_MEMORY;
    
	/* 配置数据源地址；串口0接收 */ 
	dmaStruct.periph_addr = USART0_DATA_ADDR;
	/* 配置源地址是固定的还是增长的；串口0接收 */ 
	dmaStruct.periph_inc = DMA_PERIPH_INCREASE_DISABLE;
	/* 配置源数据传输位宽；串口0接收 */ 
	dmaStruct.periph_width = DMA_PERIPHERAL_WIDTH_8BIT;
	
	/* 配置数据目的地址；串口0接收 */
	dmaStruct.memory_addr = (uint32_t)(g_rcvDataBuf);
	/* 配置目的地址是固定的还是增长的；串口0接收 */ 
	dmaStruct.memory_inc = DMA_MEMORY_INCREASE_ENABLE;
	/* 配置目的数据传输位宽；串口0接收 */ 
	dmaStruct.memory_width = DMA_MEMORY_WIDTH_8BIT;
    
	/* 配置数据传输最大次数；串口0接收 */ 
	dmaStruct.number = MAX_BUF_SIZE;
    /* 配置DMA通道优先级；串口0接收 */ 
	dmaStruct.priority = DMA_PRIORITY_HIGH;
	dma_init(g_uartHwInfo.dmaNo, g_uartHwInfo.dmaRxCh, &dmaStruct);
	    
    /* configure DMA mode 串口0接收 */
    dma_circulation_disable(g_uartHwInfo.dmaNo, g_uartHwInfo.dmaRxCh);//循环模式禁用
    dma_memory_to_memory_disable(g_uartHwInfo.dmaNo, g_uartHwInfo.dmaRxCh);
    
    usart_dma_receive_config(g_uartHwInfo.uartNo, USART_DENR_ENABLE); // 使能DMA接收功能
    
    /* 使能DMA通道；串口0接收 */ 
	dma_channel_enable(g_uartHwInfo.dmaNo, g_uartHwInfo.dmaRxCh);
    
    /* DMA 串口发送通道配置 */
    /* 复位DMA通道；串口0发送 */
	dma_deinit(g_uartHwInfo.dmaNo, g_uartHwInfo.dmaTxCh);

    dma_struct_para_init(&dmaStruct);
    
	/* 配置传输方向；*/ 
	dmaStruct.direction = DMA_MEMORY_TO_PERIPHERAL;
    
	/* 配置数据源地址；*/ 
	dmaStruct.periph_addr = USART0_DATA_ADDR;
	/* 配置源地址是固定的还是增长的；*/ 
	dmaStruct.periph_inc = DMA_PERIPH_INCREASE_DISABLE;
	/* 配置源数据传输位宽；*/ 
	dmaStruct.periph_width = DMA_PERIPHERAL_WIDTH_8BIT;
    
	/* 配置数据目的地址；*/
	dmaStruct.memory_addr = (uint32_t)(g_traDataBuf);
	/* 配置目的地址是固定的还是增长的；*/ 
	dmaStruct.memory_inc = DMA_MEMORY_INCREASE_ENABLE;
	/* 配置目的数据传输位宽；*/ 
	dmaStruct.memory_width = DMA_MEMORY_WIDTH_8BIT;
    
	/* 配置数据传输最大次数；*/ 
	dmaStruct.number = MAX_BUF_SIZE;
	/* 配置DMA通道优先级；*/ 
	dmaStruct.priority = DMA_PRIORITY_HIGH;
	dma_init(g_uartHwInfo.dmaNo, g_uartHwInfo.dmaTxCh, &dmaStruct);
    
    /* configure DMA mode */
    dma_circulation_disable(g_uartHwInfo.dmaNo, g_uartHwInfo.dmaTxCh);//循环模式禁用
    dma_memory_to_memory_disable(g_uartHwInfo.dmaNo, g_uartHwInfo.dmaTxCh);
    
    usart_dma_transmit_config(g_uartHwInfo.uartNo, USART_DENT_ENABLE); // 使能DMA发送功能
    
    /* DMA0 通道3 中断优先级设置并使能 */
	nvic_irq_enable(DMA0_Channel3_IRQn, 1, 0);
	/* 使能 DMA0 通道3 传输完成、传输错误中断 */
    dma_interrupt_enable(DMA0, DMA_CH3, DMA_INT_FTF|DMA_INT_ERR);    
    
    /* 使能DMA通道；串口0发送 */ 
//	dma_channel_enable(g_uartHwInfo.dmaNo, g_uartHwInfo.dmaTxCh);
}

/*********************************************************************************************************
* 函数名称：Usart0Init
* 函数功能：初始化串口及DMA
* 输入参数：baudRate：波特率
* 输出参数：void
* 返 回 值：void
* 创建日期：2025年01月15日
* 注    意：
*********************************************************************************************************/ 
static void Usart0Init(uint32_t baudRate)
{
	/* 使能UART时钟；*/
	rcu_periph_clock_enable(g_uartHwInfo.rcuUart);
    
	/* 复位UART；*/
	usart_deinit (g_uartHwInfo.uartNo);
	/* 在USART_BAUD寄存器中设置波特率；*/ 
	usart_baudrate_set(g_uartHwInfo.uartNo, baudRate);
	/* 在USART_CTL0寄存器中设置TEN位，使能发送功能；*/
	usart_transmit_config(g_uartHwInfo.uartNo, USART_TRANSMIT_ENABLE);
	/* 在USART_CTL0寄存器中设置TEN位，使能接收功能；*/
	usart_receive_config(g_uartHwInfo.uartNo, USART_RECEIVE_ENABLE);
    
	/* 在USART_CTL0寄存器中置位UEN位，使能UART；*/ 
	usart_enable(g_uartHwInfo.uartNo);
    
    usart_receiver_timeout_threshold_config(g_uartHwInfo.uartNo, 35); //串口接收超时阈值设置为3.5个字节
    usart_receiver_timeout_enable(g_uartHwInfo.uartNo); //使能USART接收超时
    
	/* 使能串口中断；*/
	nvic_irq_enable(g_uartHwInfo.irq, 0, 0);
    /* 使用串口接收超时中断 */
    usart_interrupt_enable(g_uartHwInfo.uartNo, USART_INT_RT); //使用串口接收超时中断

}

/*********************************************************************************************************
* 函数名称：DMASendData
* 函数功能：使用DMA发送缓冲区数据
* 输入参数：sendCount: 发送数据数量
* 输出参数：void
* 返 回 值：void
* 创建日期：2025年01月18日
* 注    意：
*********************************************************************************************************/ 
static void DMASendData(uint16_t sendCount)
{
    gpio_bit_set(g_uartHwInfo.gpio, g_uartHwInfo.rePin); //高电平，发送模式
    /* 设置DMA传输 */
    dma_channel_disable(g_uartHwInfo.dmaNo, g_uartHwInfo.dmaTxCh);		/* 关闭DMA传输才可以进行设置 */
    dma_memory_address_config(g_uartHwInfo.dmaNo, g_uartHwInfo.dmaTxCh, (uint32_t)(g_traDataBuf));
    dma_transfer_number_config(g_uartHwInfo.dmaNo, g_uartHwInfo.dmaTxCh, sendCount);
    dma_channel_enable(g_uartHwInfo.dmaNo, g_uartHwInfo.dmaTxCh);		/* 开启DMA传输 */
}
 
/*********************************************************************************************************
* 函数名称：USART0_IRQHandler
* 函数功能：串口中断服务
* 输入参数：void
* 输出参数：void
* 返 回 值：void
* 创建日期：2025年01月15日
* 注    意：
*********************************************************************************************************/
void USART0_IRQHandler(void)
{
	if (usart_interrupt_flag_get(g_uartHwInfo.uartNo, USART_INT_FLAG_RT) != RESET)
	{
		usart_interrupt_flag_clear(g_uartHwInfo.uartNo, USART_INT_FLAG_RT); //清除USART_INT_FLAG_RT标志位
		
        uint16_t receiveCount = MAX_BUF_SIZE - dma_transfer_number_get(g_uartHwInfo.dmaNo, g_uartHwInfo.dmaRxCh);
		if(receiveCount > 0) 
		{
			g_pktRcvd = true;
		}

	}
}

/*********************************************************************************************************
* 函数名称：DMA0_Channel3_IRQHandler
* 函数功能：DMA0_Channal3中断服务
* 输入参数：void
* 输出参数：void
* 返 回 值：void
* 创建日期：2025年01月18日
* 注    意：
*********************************************************************************************************/
void DMA0_Channel3_IRQHandler(void)
{
	if (dma_interrupt_flag_get(g_uartHwInfo.dmaNo, g_uartHwInfo.dmaTxCh, DMA_INT_FLAG_FTF) != RESET)
	{
        dma_interrupt_flag_clear(g_uartHwInfo.dmaNo, g_uartHwInfo.dmaTxCh, DMA_INT_FLAG_FTF);
        s_bDMASendEndStatus = true;
    }	
    
    if (dma_interrupt_flag_get(g_uartHwInfo.dmaNo, g_uartHwInfo.dmaTxCh, DMA_INT_ERR) != RESET)
	{
        dma_interrupt_flag_clear(g_uartHwInfo.dmaNo, g_uartHwInfo.dmaTxCh, DMA_INT_ERR);
    
        printf("dma send error");
    }
}

/*********************************************************************************************************
* 函数名称：fputc
* 函数功能：printf函数默认打印输出到显示器，如果要输出到串口，
            必须重新实现fputc函数，将输出指向串口，称为重定向
* 输入参数：ch，f
* 输出参数：void
* 返 回 值：int
* 创建日期：2025年01月15日
* 注    意：
*********************************************************************************************************/
int fputc(int ch, FILE *f)
{
    gpio_bit_set(g_uartHwInfo.gpio, g_uartHwInfo.rePin); //高电平，发送模式 
	usart_data_transmit(g_uartHwInfo.uartNo, (uint8_t)ch);
	while (RESET == usart_flag_get(g_uartHwInfo.uartNo, USART_FLAG_TBE)){ }
    gpio_bit_reset(g_uartHwInfo.gpio, g_uartHwInfo.rePin); //低电平，接收模式 
	return ch;
}

/*********************************************************************************************************
*                                              API函数实现
*********************************************************************************************************/
/*********************************************************************************************************
* 函数名称：InitUART0
* 函数功能：初始化串口及DMA
* 输入参数：baudRate：波特率
* 输出参数：void
* 返 回 值：void
* 创建日期：2025年01月15日
* 注    意：
*********************************************************************************************************/
void InitUART0(uint32_t baudRate)
{
	Usart0GpioInit();	
    Usart0Init(baudRate);
	Usart0DmaInit();
}

/*********************************************************************************************************
* 函数名称：ScanResendReceive
* 函数功能：将接收到的数据发送回去
* 输入参数：void
* 输出参数：void
* 返 回 值：void
* 创建日期：2025年01月17日
* 注    意：
*********************************************************************************************************/
void ScanResendReceive(void){
    if(g_pktRcvd){       
        uint16_t receiveCount = MAX_BUF_SIZE - dma_transfer_number_get(g_uartHwInfo.dmaNo, g_uartHwInfo.dmaRxCh);
        if(receiveCount > 0){
            for(uint16_t i = 0; i < receiveCount; i++){
                g_traDataBuf[i] = g_rcvDataBuf[i];
            }
        
            g_pktRcvd = false;
            
            DMASendData(receiveCount);
        }    
        
		dma_channel_disable(g_uartHwInfo.dmaNo, g_uartHwInfo.dmaRxCh);
		dma_transfer_number_config(g_uartHwInfo.dmaNo, g_uartHwInfo.dmaRxCh, MAX_BUF_SIZE);
		dma_channel_enable(g_uartHwInfo.dmaNo, g_uartHwInfo.dmaRxCh);
    }
}

/*********************************************************************************************************
* 函数名称：ScanDMASendDelay
* 函数功能：485使用DMA发送后需要延时关闭发送状态，否则无法发送成功
* 输入参数：void
* 输出参数：void
* 返 回 值：void
* 创建日期：2025年01月18日
* 注    意：需要延时200us以上，放在10us延时中重复执行20次
*********************************************************************************************************/
void ScanDMASendDelay(void)
{
    static uint16_t delayCount = 0;
    static bool delayStart = false;
    if(s_bDMASendEndStatus){
        s_bDMASendEndStatus = false;
        delayCount = DMA_SEND_DELAY_COUNT;      
        delayStart = true;        
    } else if(delayStart){
        delayCount--;
        if(delayCount == 0){
            gpio_bit_reset(g_uartHwInfo.gpio, g_uartHwInfo.rePin); //低电平，接收模式 
            delayStart = false;
        }
    }
}


/* 自定义UART0 printf 函数
 * 参数：带发送的字符串，确保一次发送数据不超过UART0_TX_LEN字节
 * 返回值：无	*/
void Usart485Printf(char* fmt,...)  
{
	uint32_t i;
	va_list ap; //定义一个指向参数的指针，可以通过指针运算来调整访问的对象。va_list可以存储可变参数
	va_start(ap, fmt);//初始化，用ap去指向fmt	
	
	vsprintf((char*)g_traDataBuf, fmt, ap);//第一个参数是指向字符数组的指针、fmt是用于指定输出的格式、包含格式化字符串中使用的可变参数
	va_end(ap);//结束可变参数的获取，清空va_list
	g_traDataBuf[MAX_BUF_SIZE - 1] = '\0';
	
	/* 计算此次发送数据的长度 */
	i = strlen((const char*)(g_traDataBuf));
	
	DMASendData(i);
}

